/***************************************************************
 Copyright © ALIENTEK Co., Ltd. 1998-2021. All rights reserved.
 文件名 : lcd_vertical_display.c
 作者 : 邓涛
 版本 : V1.0
 描述 : FrameBuffer应用编程之横屏切换为竖屏显示
 其他 : 无
 论坛 : www.openedv.com
 日志 : 初版 V1.0 2021/6/15 邓涛创建
 ***************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <string.h>

#define argb8888_to_rgb565(color)   ({ \
            unsigned int temp = (color); \
            ((temp & 0xF80000UL) >> 8) | \
            ((temp & 0xFC00UL) >> 5) | \
            ((temp & 0xF8UL) >> 3); \
            })

static int lcd_width;                   //LCD X分辨率
static int lcd_height;                  //LCD Y分辨率
static int lcd_max_y;                   //LCD Y坐标最大值
static int user_width;                  //竖屏模式下X分辨率
static int user_height;                 //竖屏模式下Y分辨率
static unsigned short *screen_base = NULL;      //映射后的显存基地址

static int lcd_coordinate_check(unsigned int dir, unsigned int *x, unsigned int *y)
{
    unsigned int max_width = 0;
    unsigned int max_height = 0;
    
    if(dir) // dir != 0 的时候竖屏显示
    {
        max_width = user_width;
        max_height = user_height;
    }
    else 
    {
        max_width = lcd_width;
        max_height = lcd_height;
    }

    /* 对传入参数的校验 */
    *x = (*x >= max_width)?(max_width - 1):*x;
    *y = (*y >= max_height)?(max_height - 1):*y;

    return 0;
}
static void lcd_draw_point(unsigned int dir, unsigned int x, unsigned int y, unsigned int color)
{
    unsigned int max_width = 0;
    unsigned int max_height = 0;
    unsigned long temp = 0;
    char tempStr[64] = {0};
    unsigned short rgb565_color = argb8888_to_rgb565(color);//得到RGB565颜色值
    
    if(dir) // dir != 0 的时候竖屏显示
    {
        max_width = user_width;
        max_height = user_height;
    }
    else 
    {
        max_width = lcd_width;
        max_height = lcd_height;
    }
    /* 对传入参数的校验 */
    x = (x >= max_width)?(max_width - 1):x;
    y = (y >= max_height)?(max_height - 1):y;

    // 计算显存的位置
    if(dir)
    {
        temp = (lcd_max_y-x) * lcd_width + y;
        snprintf(tempStr, sizeof(tempStr), "(%d-%d) * %d + %d", lcd_max_y, x, lcd_width, y);
    }
    else
    {
        temp = y * lcd_width + x;
        snprintf(tempStr, sizeof(tempStr), "%d * %d + %d", y, lcd_width, x);
    }
    printf("lcd info [%dx%d],lcd pen info:(%d, %d) temp:%s=%ld \n", max_width, max_height, x, y, tempStr, temp);
    /* 填充颜色 */
    screen_base[temp] = rgb565_color;
}

static void lcd_draw_line(unsigned int dir, unsigned int type, unsigned int x, unsigned int y,
            unsigned int length, unsigned int color)
{
    unsigned int end = 0;

    if (type) 
    {
        //水平线
        end = x + length - 1;
        lcd_coordinate_check(dir, &x, &y);
        lcd_coordinate_check(dir, &end, &y);
        printf("type=%d, length=%d, end=%d\n", type, length, end);
        for ( ; x <= end; x++)
        {
            lcd_draw_point(dir, x, y, color);
        }
    }
    else 
    {  
        //垂直线
        end = y + length - 1;
        lcd_coordinate_check(dir, &x, &y);
        lcd_coordinate_check(dir, &x, &end);
        printf("type=%d, length=%d, end=%d\n", type, length, end);
        for ( ; y <= end; y++)
        {
            lcd_draw_point(dir, x, y, color);
        }
    }

}


static void lcd_clean(unsigned int color)
{
    unsigned short rgb565_color = argb8888_to_rgb565(color);//得到RGB565颜色值
    
    memset(screen_base, rgb565_color, lcd_height*lcd_width * 2);
}


int main(int argc, char *argv[])
{
    struct fb_fix_screeninfo fb_fix;
    struct fb_var_screeninfo fb_var;
    unsigned int screen_size;
    int fd;

    /* 打开framebuffer设备 */
    if (0 > (fd = open("/dev/fb0", O_RDWR))) {
        perror("open error");
        exit(EXIT_FAILURE);
    }

    /* 获取参数信息 */
    ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
    ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);

    screen_size = fb_fix.line_length * fb_var.yres;
    lcd_width = fb_var.xres;
    lcd_height = fb_var.yres;
    lcd_max_y = lcd_height - 1;
    user_width = fb_var.yres;
    user_height = fb_var.xres;
    
    printf("分辨率: %d*%d\n"
            "像素深度bpp: %d\n"
            "一行的字节数: %d\n"
            "像素格式: R<%d %d> G<%d %d> B<%d %d>\n",
            lcd_width, lcd_height, fb_var.bits_per_pixel,
            fb_fix.line_length,
            fb_var.red.offset, fb_var.red.length,
            fb_var.green.offset, fb_var.green.length,
            fb_var.blue.offset, fb_var.blue.length);

    /* 将显示缓冲区映射到进程地址空间 */
    screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == (void *)screen_base) 
    {
        perror("mmap error");
        close(fd);
        exit(EXIT_FAILURE);
    }

    lcd_clean(0x0); //清屏（屏幕显示黑色）

    lcd_draw_point(0, 0, 0, 0xFFFFFF);
    lcd_draw_point(1, 0, 0, 0x0000FF);

    lcd_draw_line(0, 0, 0, 0, 800, 0xFFFFFF);//白色水平线
    lcd_draw_line(0, 1, 0, 0, 800, 0xFFFFFF);//白色水平线
    
    lcd_draw_line(1, 0, 100, 100, 200, 0xFFFFFF);//白色水平线
    lcd_draw_line(1, 1, 100, 100, 200, 0xFFFFFF);//白色水平线
    
    /* 退出 */
    munmap(screen_base, screen_size);  //取消映射
    close(fd);  //关闭文件
    exit(EXIT_SUCCESS);    //退出进程
}
